package net.fxug.hokuriku.chocolat.service.socketClient
{
    import flash.errors.IllegalOperationError;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.events.TimerEvent;
    import flash.net.Socket;
    import flash.utils.Timer;

    import net.fxug.hokuriku.chocolat.events.SocketEvent;
    import net.fxug.hokuriku.chocolat.events.SonarEvent;
    import net.fxug.hokuriku.chocolat.events.VoteEvent;
    import net.fxug.hokuriku.chocolat.model.networkInterface.data.NetworkInterfaceData;
    import net.fxug.hokuriku.chocolat.model.tracer.Tracer;
    import net.fxug.hokuriku.chocolat.service.socket.constants.socketConstants;
    import net.fxug.hokuriku.chocolat.service.socket.data.SocketData;
    import net.fxug.hokuriku.chocolat.service.socketClient.constants.SocketClientConstants;
    import net.fxug.hokuriku.chocolat.service.sonar.Sonar;
    import net.fxug.hokuriku.chocolat.service.sonar.constants.SonarModeConstants;
    import net.fxug.hokuriku.chocolat.service.sonar.data.SocketServerData;

    /**
     * サーバー側にTCP接続成功時にディスパッチされる。
     * Voteするたびに切断する仕様なので頻繁に投げられる。うざいなら、一度受け取ったら、接続が切れるまでLISTENしないのが吉
     *
     * @eventType net.fxug.hokuriku.chocolat.events.SocketEvent
     */
    [Event(name="ServerConnectSuccess", type="net.fxug.hokuriku.chocolat.events.SocketEvent")]

    /**
     * サーバーが見つけられなかった
     * サーバーが存在しないか、ネットワーク的に接続不可の状態なのかどちらか
     *
     * @eventType net.fxug.hokuriku.chocolat.events.SocketEvent
     */
    [Event(name="ServerConnectFailed", type="net.fxug.hokuriku.chocolat.events.SocketEvent")]

    /**
     * サーバーに接続できなくなった
     * 接続できていたサーバーにメッセージ送信もしくは接続確認した際に接続不能だった場合
     *
     * @eventType net.fxug.hokuriku.chocolat.events.SocketEvent
     */
    [Event(name="ServerConnectClose", type="net.fxug.hokuriku.chocolat.events.SocketEvent")]


    /**
     * ソケット接続クライアントクラス
     *
     * 1.UDPでブロードキャスト(Sonarクラス)サーバーを探査
     * 2.サーバーを見つけたらTCPで通信
     *
     * TODO Windows7同士の接続だと挙動がおかしいので調査
     * TODO IPv4のネットワーク情報が2つ以上見つかった場合はユーザーに選択してもらう必要ありそうだ
     * TODO 接続できなかった場合の処理、接続が切れた場合の処理
     *
     * @author mac
     */
    public class SocketClient extends EventDispatcher
    {
        // サーバー探査用クラス。DatagramSocketクラスを用いてローカル環境ないのサーバーを探索する
        private var sonar:Sonar;

        // サーバー接続用のソケットクラス
        private var socket:Socket;

        // 自分自身のネットワーク情報
        private var networkData:NetworkInterfaceData;

        // 接続先のサーバー情報
        private var serverData:SocketServerData;

        // 送信するメッセージデータ
        private var tmpMessage:XML = new XML();

        private var timer:Timer = new Timer(SocketClientConstants.CHECK_INTERVAL);

        /**
         * コンストラクタ
         *
         * @param inNetworkData
         * @throws IllegalOperationError
         */
        public function SocketClient(inNetworkData:NetworkInterfaceData)
        {
            if (inNetworkData is NetworkInterfaceData)
            {
                this.networkData = inNetworkData;
            }
            else
            {
                throw new IllegalOperationError("Illigal Constructor param error");
            }
        }

        /**
         * IP指定でサーバー探査
         */
        public function connectByAddress(address:String):void
        {
            Tracer.t("SocketClient::connectByAddress", true);

            this.ping(address);
        }

        /**
         * ブロードキャストを用いてサーバー探査
         *
         */
        public function connectByBroadcast():void
        {
            Tracer.t("SocketClient::connectByBroadcast");

            this.ping(this.networkData.broadcast);
        }

        /**
         * サーバー探査
         */
        private function ping(address:String):void
        {
            Tracer.t("SocketClient::Ping for " + address);

            if (this.sonar)
            {
                try
                {
                    this.sonar.ping(address);
                }
                catch (e:Error)
                {
                    Tracer.t("SocketClient::Ping catch error message = " + e.message);
                    throw new Error(e.message);
                }
            }
            else
            {
                Tracer.t("SocketClient:: new Sonar address" + this.networkData.address);

                this.sonar = new Sonar(this.networkData.address, SonarModeConstants.CLIENT_MODE);
                this.sonar.addEventListener(SonarEvent.SERVER_REPLY_RECIEVED, this.serverReplyRecievedEventHandler);
                this.sonar.addEventListener(SonarEvent.SERVER_NOT_DISCOVER, this.serverNotDiscoverdEventHandler);

                try
                {
                    this.sonar.ping(address);
                }
                catch (e:Error)
                {
                    Tracer.t("SocketClient:: new Error" + e.message);
                    throw new Error(e.message);
                }
            }
        }

        /**
         * ソナーがサーバーからの返答を受け取ったイベントハンドラ
         *
         */
        private function serverReplyRecievedEventHandler(event:SonarEvent):void
        {
            Tracer.t("SocketClient::serverReplyRecievedEventHandler");

            this.serverData = event.serverData;

            this.connectServer();
        }

        private function connectServer():void
        {
            if (this.socket)
            {
                if (this.socket.connected)
                {
                    this.socket.close();
                }
                this.socket = null;
            }

            Tracer.t("Create Socket port = " + socketConstants.PORT + " / address = " + this.serverData.address);

            this.socket = new Socket();

            this.socket.addEventListener(Event.CLOSE, this.socketCloseEventHandler);
            this.socket.addEventListener(Event.CONNECT, this.serverConnectEventHandler);
            this.socket.addEventListener(IOErrorEvent.IO_ERROR, this.serverConnectIOError);
            this.socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this.securityErrorEventHandler);

            this.socket.connect(this.serverData.address, socketConstants.PORT);
        }

        public function dispose():void
        {
            Tracer.t("SocketClient::dispose");

            if (this.sonar)
            {
                this.sonar.removeEventListener(SonarEvent.SERVER_REPLY_RECIEVED, this.serverReplyRecievedEventHandler);
                this.sonar.removeEventListener(SonarEvent.SERVER_NOT_DISCOVER, this.serverNotDiscoverdEventHandler);
                this.sonar.dispose();
            }

            if (this.socket)
            {
                this.socket.removeEventListener(Event.CLOSE, this.socketCloseEventHandler);
                this.socket.removeEventListener(Event.CONNECT, this.serverConnectEventHandler);
                this.socket.removeEventListener(IOErrorEvent.IO_ERROR, this.serverConnectIOError);
                this.socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, this.securityErrorEventHandler);

                if(this.socket.connected) {
                    this.socket.close();
                }
            }
        }

        /**
         * メッセージを送信する
         *
         * @param message
         */
        public function sendMessage(message:String, vote:Boolean, name:String = ""):void
        {
            var voteValue:String;
            if (vote == true)
            {
                voteValue = VoteEvent.GOOD_EVENT;
            }
            else if (vote == false)
            {
                voteValue = VoteEvent.BAD_EVENT;
            }
            this.tmpMessage = SocketData.getSocketDataForMessage(message, voteValue, name)

            if (this.socket.connected)
            {
                Tracer.t("発言=" + tmpMessage.toString());

                this.socket.writeUTFBytes(tmpMessage.toXMLString());
                this.socket.flush();
                this.socket.close();

                this.tmpMessage = null;
            }
            else
            {
                this.connectServer();
            }
        }


        private function serverConnectEventHandler(event:Event):void
        {
            Tracer.t("SocketClient::serverConnectEventHandler");
            Tracer.t("    サーバーに接続成功!!");

            Tracer.t("    socket.connected = " + this.socket.connected);

            var socket_event:SocketEvent = new SocketEvent(SocketEvent.SERVER_CONNECT_SUCCESS);
            socket_event.networkData = this.networkData;
            this.dispatchEvent(socket_event);

            if (this.tmpMessage)
            {
                this.socket.writeUTFBytes(tmpMessage.toXMLString());
                this.socket.flush();
                this.socket.close();
            }
            this.tmpMessage = null;

            if (!this.timer.running)
            {
                Tracer.t("    生存確認タイマー開始");

                // 定期的に接続を確認するタイマーをスタート
                this.timer.addEventListener(TimerEvent.TIMER, this.checkServerAliveEventHandler);
                this.timer.start();
            }
        }

        private function checkServerAliveEventHandler(event:TimerEvent):void
        {
            Tracer.t("SocketClient::checkServerAliveEventHandler");
            Tracer.t("    サーバー生存確認を開始");

            this.connectServer();
        }

        /**
         * サーバーを見つけることができなかった
         */
        private function serverNotDiscoverdEventHandler(event:SonarEvent):void
        {
            Tracer.t("SocketClient::serverNotDiscoverdEventHandler");
            Tracer.t("    サーバーを見つけることができませんでした");

            var socket_event:SocketEvent = new SocketEvent(SocketEvent.SERVER_CONNECT_FAILED);
            this.networkData = this.networkData;

            this.dispatchEvent(socket_event);
        }

        /**
         * 接続エラー(一度接続したサーバーなどが終了などの理由で接続できなかった場合に発生)
         */
        private function serverConnectIOError(event:IOErrorEvent):void
        {
            Tracer.t("SocketClient::serverConnectIOError");

            var socket_event:SocketEvent = new SocketEvent(SocketEvent.SERVER_CONNECT_CLOSE);
            this.dispatchEvent(socket_event);

            if (this.timer)
            {
                Tracer.t("     タイーマー停止");
                this.timer.stop();
            }
        }

        /**
         *
         * @param event
         */
        private function socketCloseEventHandler(event:Event):void
        {
            Tracer.t("SocketClient::socketCloseEventHandler");

            this.dispose();
        }

        private function securityErrorEventHandler(event:SecurityErrorEvent):void
        {
            Tracer.t("SocketClient::securityErrorEventHandler");

            throw new Error("SocketClient::securityError");

            this.dispose();
        }
    }
}